package queue

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

type Actor struct {
	actorId int64
	doChan  chan struct{}
}

type ActorQueue struct {
	lc       sync.Mutex
	seq      uint64
	limit    uint64
	state    uint64
	pending  map[uint64]*Actor
	actorMap map[int64]bool
}

var configInstanceOne sync.Once
var instance *ActorQueue

func NewActorQueue(limit uint64) *ActorQueue {
	configInstanceOne.Do(func() {
		instance = &ActorQueue{
			seq:      0,
			limit:    limit,
			state:    0,
			pending:  make(map[uint64]*Actor, 0),
			actorMap: make(map[int64]bool, limit),
		}
	})
	return instance
}

func (a *ActorQueue) Acquire(userId int64, timeout time.Duration) error {
	if timeout == 0 {
		return errors.New("time out error")
	}

	a.lc.Lock()
	if a.state < a.limit && !a.actorMap[userId] {
		a.state++
		a.actorMap[userId] = true
		a.lc.Unlock()
		return nil
	}

	a.seq++
	fmt.Println("seq:", a.seq)
	seq := a.seq
	actor := &Actor{actorId: userId, doChan: make(chan struct{})}
	a.pending[seq] = actor
	a.lc.Unlock()

	select {
	case <-actor.doChan:
		return nil
	case <-time.After(timeout):
		a.lc.Lock()
		delete(a.pending, seq)
		a.lc.Unlock()
		return errors.New("time out")
	}
	return nil
}

func (a *ActorQueue) Release(userId int64) error {
	a.lc.Lock()
	defer a.lc.Unlock()

	if !a.actorMap[userId] {
		return errors.New("release called without a successful Acquire")
	}

	a.state--
	delete(a.actorMap, userId)

	for seq, actor := range a.pending {
		if a.actorMap[actor.actorId] {
			continue
		}
		a.state++
		a.actorMap[actor.actorId] = true
		close(actor.doChan)
		delete(a.pending, seq)
		return nil
	}

	return nil

}
